
#include <assert.h>
#include <memory.h>

#include "lg_vertex.h"

#define LGOGL_CORE

#include "lgogl_vertex.h"

typedef struct VertexData VertexData;
struct VertexData {
	char* data;
	unsigned count;
	unsigned cap;
	VertexData* next;
};

typedef struct VertexIndex VertexIndex;
struct VertexIndex {
	unsigned* indexs;
	unsigned count;
	unsigned cap;
	VertexIndex* next;
};

typedef struct {
	LGVertex vertex;
	GLuint glVao;
	GLuint glVbo;
	GLuint glIbo;
	unsigned glSize;
	VertexData data;
	VertexIndex indexs;
}LGOGLVertex;

static struct {
	LGVertex* curBind;
}ctx = { 0 };

static unsigned TYPE_INDEAXS[] = {
	0, 1, 2, 3, 4, 5, 6, 7
};

static void
initData(LGVertex* vertex, VertexData* data, unsigned cap) {
	data->cap = cap;
	data->count = 0;
	data->next = NULL;
	data->data = malloc(vertex->eleSize * cap);
}

static void
initIndexs(LGVertex* vertex, VertexIndex* indexs, unsigned cap) {
	cap *= 2;
	indexs->cap = cap;
	indexs->count = 0;
	indexs->next = NULL;
	indexs->indexs = malloc(sizeof(unsigned) * cap);
}


static void
expandData(LGOGLVertex* oglVertex) {
	VertexData* next = &oglVertex->data;
	while (next->next) {
		next = next->next;
	}
	unsigned cap = next->cap * 2;
	VertexData* n = malloc(sizeof(VertexData));
	n->cap = cap;
	n->count = 0;
	n->next = NULL;
	n->data = malloc(oglVertex->glSize * cap);
	next->next = n;
}

static void
expandIndexs(LGOGLVertex* oglVertex) {
	VertexIndex* next = &oglVertex->indexs;
	while (next->next) {
		next = next->next;
	}
	unsigned cap = next->cap * 2;
	VertexIndex* n = malloc(sizeof(VertexIndex));
	n->cap = cap;
	n->count = 0;
	n->next = NULL;
	n->indexs = malloc(sizeof(unsigned) * cap);
	next->next = n;
}


static void
enableVertexAttr(LGVertex* vertex) {
	LGOGLVertex* oglVertex = (LGOGLVertex*)vertex;
	LGVertexAttr* attr;
	int index;
	char* offset = NULL;
	for (unsigned i = 0; i < vertex->attrCount; i++) {
		attr = &vertex->attrs[i];
		index = TYPE_INDEAXS[attr->type];
		offset = (char*)(sizeof(GLfloat) * 4 * i);
		glVertexAttribPointer(index, 4, GL_FLOAT,
			GL_FALSE, oglVertex->glSize, (void*)offset);
		glEnableVertexAttribArray(index);
	}
}

//static void
//enableVertexAttr(LGVertex* vertex) {
//	LGOGLVertex* oglVertex = (LGOGLVertex*)vertex;
//	GLint size;
//	GLenum type;
//	GLboolean normalized;
//	char* offset = NULL;
//	unsigned addOffset = 0;
//	LGVertexAttr* attr;
//	for (unsigned i = 0; i < vertex->attrCount; i++) {
//		attr = &vertex->attrs[i];
//		switch (attr->type) {
//		case LG_INT:
//			size = 1; type = GL_INT; normalized = GL_FALSE;
//			addOffset = sizeof(GLint);
//			break;
//		case LG_UNSIGNED:
//			size = 1; type = GL_UNSIGNED_INT; normalized = GL_FALSE;
//			addOffset = sizeof(GLuint);
//			break;
//		case LG_FLOAT:
//			size = 1; type = GL_FLOAT; normalized = GL_FALSE;
//			addOffset = sizeof(GLfloat);
//			break;
//		case LG_VECTOR:
//			size = 4, type = GL_FLOAT, normalized = GL_FALSE;
//			addOffset = sizeof(GLfloat) * 4;
//			break;
//		case LG_MATRIX:
//			size = 16, type = GL_FLOAT, normalized = GL_FALSE;
//			addOffset = sizeof(GLfloat) * 16;
//			break;
//		default:
//			assert(0);
//		}
//		glVertexAttribPointer(i, size, type,
//			normalized, oglVertex->eleSize, (void*)offset);
//		glEnableVertexAttribArray(i);
//		offset += addOffset;
//	}
//}



static unsigned
getTypeSize(int type) {
	switch (type) {
	case LG_TINT:
		return sizeof(GLint);
	case LG_TUNSIGNED:
		return sizeof(GLuint);
	case LG_TFLOAT:
		return sizeof(GLfloat);
	case LG_TVECTOR:
		return sizeof(GLfloat) * 4;
	case LG_TMATRIX:
		return sizeof(GLfloat) * 16;
	default:
		return 0;
	}
}


//static char*
//copyVertex(LGVertex* vertex, char* src, char* dest) {
//	LGVertexAttr* attr;
//	for (unsigned i = 0; i < vertex->attrCount; i++) {
//		attr = &vertex->attrs[i];
//		switch (attr->type) {
//		case LG_INT:
//			*(GLint*)dest = *(int*)src;
//			dest += sizeof(GLint); src += sizeof(int);
//			break;
//		case LG_UNSIGNED:
//			*(GLuint*)dest = *(unsigned*)src;
//			dest += sizeof(GLuint);  src += sizeof(unsigned);
//			break;
//		case LG_FLOAT:
//			*(GLfloat*)dest = (GLfloat)(*(LGFloat*)src);
//			dest += sizeof(GLfloat);  src += sizeof(LGFloat);
//			break;
//		case LG_VECTOR: {
//			LGVector* v = (LGVector*)src;
//			GLfloat* out = (GLfloat*)dest;
//			out[0] = (GLfloat)v->x; out[1] = (GLfloat)v->y;
//			out[2] = (GLfloat)v->z; out[3] = (GLfloat)v->w;
//			dest += sizeof(GLfloat) * 4;  src += sizeof(LGVector);
//			break;
//		}
//		case LG_MATRIX: {
//			LGMatrix* m = (LGMatrix*)src;
//			GLfloat* out = (GLfloat*)dest;
//			for (unsigned i = 0; i < 16; i++) {
//				out[i] = (GLfloat)m->B[i];
//			}
//			dest += sizeof(GLfloat) * 16;  src += sizeof(LGMatrix);
//			break;
//		}
//		default:
//			assert(0);
//			break;
//		}
//	}
//	return dest;
//}

static void
copyVertex(LGVertex* vertex, char* src, char* dest) {
	LGVertexAttr* attr;
	for (unsigned i = 0; i < vertex->attrCount; i++) {
		attr = &vertex->attrs[i];
		LGVector* v = (LGVector*)(src + (int)attr->offset);
		GLfloat* out = (GLfloat*)dest;
		out[0] = (GLfloat)v->x; out[1] = (GLfloat)v->y;
		out[2] = (GLfloat)v->z; out[3] = (GLfloat)v->w;
		dest += sizeof(GLfloat) * 4;
	}
}

static void
uploadData(LGVertex* vertex) {
	LGOGLVertex* oglVertex = (LGOGLVertex*)vertex;
	unsigned glSize = oglVertex->glSize;
	unsigned totalSize = glSize * vertex->totalData;
	glBindBuffer(GL_ARRAY_BUFFER, oglVertex->glVbo);
	glBufferData(GL_ARRAY_BUFFER, totalSize, NULL, GL_STATIC_DRAW);
	GLintptr offset = 0;
	GLsizeiptr subSize = 0;
	VertexData* next = &oglVertex->data;
	while (next && next->count) {
		subSize = next->count * glSize;
		LGVector temp[100];
		memcpy(temp, next->data, subSize);
		glBufferSubData(GL_ARRAY_BUFFER, offset, subSize, next->data);
		offset += subSize;
		next->count = 0;
		next = next->next;
	}
}

static void
uploadIndexs(LGVertex* vertex) {
	LGOGLVertex* oglVertex = (LGOGLVertex*)vertex;
	unsigned totalSize = sizeof(unsigned) * vertex->totalIndexs;
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, oglVertex->glIbo);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, totalSize, NULL, GL_STATIC_DRAW);
	GLintptr offset = 0;
	GLsizeiptr subSize = 0;
	VertexIndex* next = &oglVertex->indexs;
	while (next && next->count) {
		subSize = next->count * sizeof(unsigned);
		glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, subSize, next->indexs);
		offset += next->count * sizeof(unsigned);
		next->count = 0;
		next = next->next;
	}
}


static size_t
sizeofVertex() {
    return sizeof(LGOGLVertex);
}

static int
newVertex(LGVertex* vertex, unsigned cap) {
	LGOGLVertex* oglVertex = (LGOGLVertex*)vertex;
	unsigned glSize = 0;
	LGVertexAttr* attr;
	for (unsigned i = 0; i < vertex->attrCount; i++) {
		attr = &vertex->attrs[i];
		glSize += sizeof(GLfloat) * 4;
	}
	oglVertex->glSize = glSize;

	glGenVertexArrays(1, &oglVertex->glVao);
	glBindVertexArray(oglVertex->glVao);
	glGenBuffers(1, &oglVertex->glVbo);
	glGenBuffers(1, &oglVertex->glIbo);
	glBindBuffer(GL_ARRAY_BUFFER, oglVertex->glVbo);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, oglVertex->glIbo);
	enableVertexAttr(vertex);
	ctx.curBind = vertex;

	initData(vertex, &oglVertex->data, cap);
	initIndexs(vertex, &oglVertex->indexs, cap);
	return 0;
}


static void
clearVertex(LGVertex* vertex) {
	LGOGLVertex* oglVertex = (LGOGLVertex*)vertex;
	VertexData* data = &oglVertex->data;
	while (data) {
		data->count = 0;
		data = data->next;
	}
	VertexIndex* indexs = &oglVertex->indexs;
	while (indexs) {
		indexs->count = 0;
		indexs = indexs->next;
	}
	ctx.curBind = vertex;
	glBindVertexArray(oglVertex->glVao);
	glBindBuffer(GL_ARRAY_BUFFER, oglVertex->glVbo);
	glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STATIC_DRAW);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, oglVertex->glIbo);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, 0, NULL, GL_STATIC_DRAW);
}

static void
clearVertexCap(LGVertex* vertex) {
	LGOGLVertex* oglVertex = (LGOGLVertex*)vertex;
	VertexData* data = &oglVertex->data;
	data->count = 0;
	VertexData* dataTemp = data;
	data = data->next;
	dataTemp->next = NULL;
	dataTemp->count = 0;
	while (data) {
		free(data->data);
		dataTemp = data;
		data = data->next;
		free(dataTemp);
	}

	VertexIndex* indexs = &oglVertex->indexs;
	VertexIndex* indexsTemp = indexs;
	indexs->count = 0;
	indexs = indexs->next;
	indexsTemp->next = NULL;
	indexsTemp->count = 0;
	while (indexs) {
		free(indexs->indexs);
		indexsTemp = indexs;
		indexs = indexs->next;
		free(indexsTemp);
	}
	ctx.curBind = vertex;
	glBindVertexArray(oglVertex->glVao);
	glBindBuffer(GL_ARRAY_BUFFER, oglVertex->glVbo);
	glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STATIC_DRAW);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, oglVertex->glIbo);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, 0, NULL, GL_STATIC_DRAW);
}

static void
freeVertex(LGVertex* vertex) {
	clearVertexCap(vertex);
	LGOGLVertex* oglVertex = (LGOGLVertex*)vertex;
	glDeleteBuffers(1, &oglVertex->glVbo);
	glDeleteBuffers(1, &oglVertex->glIbo);
	glDeleteVertexArrays(1, &oglVertex->glVao);
	if (ctx.curBind == vertex) {
		ctx.curBind = NULL;
	}
}



static void
uploadVertex(LGVertex* vertex) {
	LGOGLVertex* oglVertex = (LGOGLVertex*)vertex;
	ctx.curBind = vertex;
	glBindVertexArray(oglVertex->glVao);
	uploadData(vertex);
	uploadIndexs(vertex);
}

static void
pushData(LGVertex* vertex, char* p, unsigned count) {
	LGOGLVertex* oglVertex = (LGOGLVertex*)vertex;
	unsigned copy = 0;
	unsigned index = 0;
	unsigned glSize = oglVertex->glSize;
	unsigned eleSize = vertex->eleSize;
	VertexData* next = &oglVertex->data;
	while (next && count > 0) {
		if (next->count >= next->cap) {
			next = next->next;
			continue;
		}
		copy = next->cap - count;
		if (copy > count) { copy = count; }
		for (unsigned i = 0; i < copy; i++) {
			copyVertex(vertex,
				p + ((index + i) * eleSize),
				next->data + ((next->count + i) * glSize));
		}
		count -= copy;
		index += copy;
		next->count += copy;
		next = next->next;
	}
	if (count > 0) {
		expandData(oglVertex);
		lgPushVertexData(vertex, p + (index * eleSize), count);
	}
}

static void
pushIndexs(LGVertex* vertex, unsigned* p, unsigned count) {
	LGOGLVertex* oglVertex = (LGOGLVertex*)vertex;
	unsigned copy = 0;
	unsigned index = 0;
	VertexIndex* next = &oglVertex->indexs;
	while (next && count > 0) {
		if (next->count >= next->cap) {
			next = next->next;
			continue;
		}
		copy = next->cap - count;
		if (copy > count) { copy = count; }
		memcpy(next->indexs + next->count,
			p + index, sizeof(unsigned) * copy);
		count -= copy;
		index += copy;
		next->count += copy;
		next = next->next;
	}
	if (count > 0) {
		expandIndexs(oglVertex);
		lgPushVertexIndexs(vertex, p + (index), count);
	}
}

static LGVertexBackend backend = {
	sizeofVertex,
	newVertex,
	freeVertex,
	clearVertex,
	clearVertexCap,
	uploadVertex,
	pushData,
	pushIndexs,
};

LGOGL_API LGVertexBackend*
lgoglVertexBackend() {
	return &backend;
}

LGOGL_API void
lgoglBindVertex(LGVertex* vertex) {
	if (ctx.curBind == vertex) {
		return;
	}
	ctx.curBind = vertex;

	LGOGLVertex* oglVertex = (LGOGLVertex*)vertex;
	glBindVertexArray(oglVertex->glVao);
	glBindBuffer(GL_ARRAY_BUFFER, oglVertex->glVbo);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, oglVertex->glIbo);
}
